package com.ahsjdy.st.stservice.pay;

import com.ahsjdy.st.stdao.entity.Order;
import com.ahsjdy.stcommon.dto.PayOrderDTO;
import com.ahsjdy.stcommon.exception.BizException;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;

import static com.github.wxpay.sdk.WXPayUtil.MD5;

@Component
public class WxpayHandler {

    private static final Logger log = LoggerFactory.getLogger(WxpayHandler.class);

    @Value("${wx.pay.unified.order.url}")
    private String unifiedOrderUrl;

    @Value("${wx.pay.query.order.url}")
    private String queryOrderUrl;

    @Value("${wx.pay.refund.order.url}")
    private String refundOrderUrl;

    @Value("${wx.pay.refund.notify.url}")
    private String refundNotifyOrderUrl;

    @Value("${wx.pay.appid}")
    private String appId;

    @Value("${wx.pay.mch.id}")
    private String mchId;

    @Value("${wx.pay.notify.url}")
    private String notifyUrl;

    @Value("${wx.pay.key}")
    private String key;

    //支付证书认证
    private static SSLContext sslContext;

    private SSLContext initSSLContext() {

        //TODO 证书位置
        try (FileInputStream inputStream = new FileInputStream("/fld/key/xxxxxkey")) {

            KeyStore keystore = KeyStore.getInstance("PKCS12");
            char[] partnerId2charArray = mchId.toCharArray();
            keystore.load(inputStream, partnerId2charArray);
            sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
            return sslContext;

        } catch (Exception e) {
            log.error("商户证书不正确 " + e.getMessage(), e);
            throw new BizException("证书不正确");
        }
    }

    //微信双向请求
    private String ssl(String url, String data){
        StringBuffer message = new StringBuffer();
        try {
            //  KeyStore keyStore  = KeyStore.getInstance("PKCS12");
            SSLContext sslcontext = initSSLContext();
            SSLConnectionSocketFactory sslsf =
                    new SSLConnectionSocketFactory(sslcontext,
                            new String[] { "TLSv1" },
                            null,
                            SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpclient = HttpClients.custom().
                    setSSLSocketFactory(sslsf).build();

            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Accept", "*/*");
            httpPost.setEntity(new StringEntity(data, "UTF-8"));

            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();

                if (entity != null) {
                    log.info("Response content length: " + entity.getContentLength());
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
                    String text;
                    while ((text = bufferedReader.readLine()) != null) {
                        message.append(text);
                    }
                }
                EntityUtils.consume(entity);
            } catch (IOException e) {
                log.error("发起微信支付请求过程异常 " + e);
                e.printStackTrace();
            } finally {
                response.close();
            }
        } catch (Exception e1) {
            log.error("发起微信支付请求异常 " + e1);
            e1.printStackTrace();
        }

        return message.toString();
    }

    //解密退款加密参数
    private String getRefundDecrypt(String reqInfoSecret) {
        String result = "";
        try {
            Security.addProvider(new BouncyCastleProvider());
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            byte[] bt = decoder.decodeBuffer(reqInfoSecret);
            String b = new String(bt);

            String md5key = MD5(key).toLowerCase();
            SecretKey secretKey = new SecretKeySpec(md5key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] resultbt = cipher.doFinal(bt);
            result = new String(resultbt,"UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    //退款
    public NotifyHolder refundOrder(Order order) {
        try {
            Map<String, String> reqData = new HashMap<String, String>();
//            reqData.put("out_trade_no", order.getBizOrderId());
//            reqData.put("out_refund_no", order.getBizOrderId());
            //TODO 上线时把金额改掉
//            reqData.put("total_fee", MoneyUtils.yuanTofen(order.getPayAmount()) + "");
//            reqData.put("refund_fee", MoneyUtils.yuanTofen(order.getPayAmount()) + "");

            reqData.put("total_fee", "2");
            reqData.put("refund_fee", "2");
            reqData.put("notify_url", refundNotifyOrderUrl);

            reqData = fillRequestData(reqData);


            String xmlMsg = ssl(refundOrderUrl, WXPayUtil.mapToXml(reqData));
            System.out.println(">>>>>微信统一支付返回数据=" + xmlMsg);

            Map<String, String> respData = processResponseXml(xmlMsg);

            String returnCode = respData.get("return_code");
            String resultCode = respData.get("result_code");

            if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode)) {
                return NotifyHolder.fail();
            }

            System.out.println("退款订单返回=" + JSON.toJSONString(respData));

            NotifyHolder notifyHolder = NotifyHolder.succ(respData.get("out_trade_no"),
                    respData.get("transaction_id"),
                    respData.get("total_fee"));


            return notifyHolder;

        } catch(Exception e) {
            e.printStackTrace();
            return NotifyHolder.fail();
        }
    }


    public NotifyHolder queryOrder(Order order) {

        try {
            Map<String, String> reqData = new HashMap<String, String>();
//            reqData.put("out_trade_no", order.getBizOrderId());

            reqData = fillRequestData(reqData);

            String xmlMsg = requestWithoutCert(queryOrderUrl, reqData, 10000, 10000);
            System.out.println(">>>>>微信支付订单查询返回数据=" + xmlMsg);

            Map<String, String> respData = processResponseXml(xmlMsg);

            String returnCode = respData.get("return_code");
            String resultCode = respData.get("result_code");
            String tradeState = respData.get("trade_state");

            if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode)
                    || !"SUCCESS".equals(tradeState)) {
                return NotifyHolder.fail();
            }

            System.out.println("统一订单返回=" + JSON.toJSONString(respData));

            NotifyHolder notifyHolder = NotifyHolder.succ(respData.get("out_trade_no"),
                    respData.get("transaction_id"),
                    respData.get("total_fee"));


            return notifyHolder;

        } catch(Exception e) {
            e.printStackTrace();
            return NotifyHolder.fail();
        }
    }


    public PayOrderDTO createPayRequest(Order order, String ip, String openId) {

        try {
            Map<String, String> reqData = new HashMap<String, String>();
            reqData.put("body", "速虎外卖");
//            reqData.put("out_trade_no", order.getBizOrderId());
            //TODO 改为真实支付金额
//            reqData.put("total_fee", MoneyUtils.yuanTofen(order.getActualAmount()) + "");//总金额单位：分
            reqData.put("total_fee", "2");//总金额单位：分
            reqData.put("spbill_create_ip", ip);
            reqData.put("notify_url", notifyUrl);
            reqData.put("trade_type", "JSAPI");
            reqData.put("openid", openId);

            reqData = fillRequestData(reqData);

            String xmlMsg = requestWithoutCert(unifiedOrderUrl, reqData, 10000, 10000);
            System.out.println(">>>>>微信统一支付返回数据=" + xmlMsg);

            Map<String, String> respData = processResponseXml(xmlMsg);

            String returnCode = respData.get("return_code");
            String resultCode = respData.get("result_code");
            if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode)) {
                throw new BizException("订单创建失败");
            }

            System.out.println("统一订单返回=" + JSON.toJSONString(respData));

            PayOrderDTO dto = new PayOrderDTO();
            dto.setNonceStr(respData.get("nonce_str"));
            dto.setPackageValue("prepay_id=" + respData.get("prepay_id"));
            dto.setTimeStamp(System.currentTimeMillis() / 1000 + "");
            dto.setSignType("MD5");


            StringBuilder sb = new StringBuilder();
            sb.append("appId=").append(appId).append("&")
                    .append("nonceStr=").append(dto.getNonceStr()).append("&")
                    .append("package=").append(dto.getPackageValue()).append("&")
                    .append("signType=").append(dto.getSignType()).append("&")
                    .append("timeStamp=").append(dto.getTimeStamp()).append("&")
                    .append("key=").append(key);

            dto.setPaySign(MD5(sb.toString()).toUpperCase());

//            appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx2017033010242291fcfe0db70013231072&signType=MD5&timeStamp=1490840662&key=qazwsxedcrfvtgbyhnujmikolp111111
            return dto;

        } catch(Exception e) {
            e.printStackTrace();
            throw new BizException("生成微信订单失败");
        }
    }

    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
        reqData.put("appid", appId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
        reqData.put("sign_type", "MD5");
        reqData.put("sign", WXPayUtil.generateSignature(reqData, key, WXPayConstants.SignType.MD5));
        return reqData;
    }

    /**
     * 判断xml数据的sign是否有效，必须包含sign字段，否则返回false。
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
        // 返回数据的签名方式和请求中给定的签名方式是一致的
        return WXPayUtil.isSignatureValid(reqData, key, WXPayConstants.SignType.MD5);
    }

    public NotifyHolder notifyProcess(String xmlStr) throws Exception{

        Map<String, String> reqData = WXPayUtil.xmlToMap(xmlStr);

        if (!"SUCCESS".equals(reqData.get("return_code"))) {
            log.info("微信异步通知信息失败");
            return NotifyHolder.fail();
        }

        boolean success = isPayResultNotifySignatureValid(reqData);
        if (!success) {
            log.info("微信异步通知验签失败，订单号：" + reqData.get("out_trade_no"));
            return NotifyHolder.fail();
        }

        if ("SUCCESS".equals(reqData.get("result_code"))) {
            return NotifyHolder.succ(reqData.get("out_trade_no"), reqData.get("transaction_id"), reqData.get("total_fee"));
        }

        return NotifyHolder.fail();
    }

    //退款通知
    public NotifyHolder notifyRefundProcess(String xmlStr) throws Exception{

        Map<String, String> reqData = WXPayUtil.xmlToMap(xmlStr);
        if (!"SUCCESS".equals(reqData.get("return_code"))) {
            return NotifyHolder.fail();
        }

        String reqInfo = reqData.get("req_info").toString();
        String refundDecrypt = getRefundDecrypt(reqInfo);
        log.info("退款通知解密数据 {}", refundDecrypt);

        if (StringUtils.isBlank(refundDecrypt)) {
            return NotifyHolder.fail();
        }

        Map<String, String> refundData = WXPayUtil.xmlToMap(refundDecrypt);

        if (!"refundData".equals(refundData.get("refund_status"))) {
            return NotifyHolder.fail();
        }

        return NotifyHolder.succ(refundData.get("out_trade_no"), refundData.get("transaction_id"), refundData.get("total_fee"));

    }

    /**
     * 校验异步通知参数
     * @param reqData
     * @return
     * @throws Exception
     */
    private boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
        WXPayConstants.SignType signType;
        if (signTypeInData == null) {
            signType = WXPayConstants.SignType.MD5;
        } else {
            signTypeInData = signTypeInData.trim();
            if (signTypeInData.length() == 0) {
                signType = WXPayConstants.SignType.MD5;
            } else if (WXPayConstants.MD5.equals(signTypeInData)) {
                signType = WXPayConstants.SignType.MD5;
            } else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
                signType = WXPayConstants.SignType.HMACSHA256;
            } else {
                throw new BizException("微信支付验签方式错误");
            }
        }

        return WXPayUtil.isSignatureValid(reqData, key, signType);
    }


    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
        String RETURN_CODE = "return_code";
        String return_code;
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        if (respData.containsKey(RETURN_CODE)) {
            return_code = respData.get(RETURN_CODE);
        } else {
            throw new BizException("微信统一支付没有返回code");
        }

        if (return_code.equals(WXPayConstants.FAIL)) {
            throw new BizException("微信统一支付请求失败");
        } else if (return_code.equals(WXPayConstants.SUCCESS)) {
            if (!"SUCCESS".equals(respData.get("result_code"))) {
                throw new BizException("微信统一支付业务参数失败");
            }

            if (isResponseSignatureValid(respData)) {
                return respData;
            } else {
                throw new BizException("微信统一支付校验失败");
            }
        }

        throw new BizException("微信统一支付错误");
    }


    public String requestWithoutCert(String strUrl, Map<String, String> reqData, int connectTimeoutMs,
                                     int readTimeoutMs) throws Exception {

        String UTF8 = "UTF-8";
        String reqBody = WXPayUtil.mapToXml(reqData);
        log.info(">>>>>>微信统一支付请求内容=" + reqBody);

        URL httpUrl = new URL(strUrl);
        HttpURLConnection httpURLConnection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        OutputStream outputStream = null;
        StringBuffer stringBuffer = null;
        String resp = "";

        try {
            httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setConnectTimeout(connectTimeoutMs);
            httpURLConnection.setReadTimeout(readTimeoutMs);
            httpURLConnection.connect();
            outputStream = httpURLConnection.getOutputStream();
            outputStream.write(reqBody.getBytes(UTF8));

            // 获取内容
            inputStream = httpURLConnection.getInputStream();
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
            stringBuffer = new StringBuffer();
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }

            resp = stringBuffer.toString();

        } finally {
            try {
                if (stringBuffer != null) {
                    bufferedReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }

            } catch(Exception ee) {
                ee.printStackTrace();
            }

        }

        return resp;
    }

    public static void main(String[] args) {
//        WxpayHandler wx = new WxpayHandler();
//        Order order = new Order();
//        order.setIdOut("2019061212121290912");
//        order.setActualAmount("0.02");
//        String payRequest = wx.createPayRequest(order, "114.221.157.44");
//        QRCodeUtils.createQRCode(payRequest, "wxcode.png", "/Users/xuetaowang/Downloads/");

    }
}
